import os
import numpy as np
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from PIL import Image, ImageTk
import tempfile
import subprocess
import sys
import threading
import time
import traceback
import logging

# 配置日志记录
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
    filename='yuv_converter_debug.log',
    filemode='w'
)

class YUVtoBMPConverter:
    def __init__(self, root):
        self.root = root
        self.root.title("YUV转BMP转换器")
        self.root.geometry("850x650")
        
        # 变量初始化
        self.yuv_format = tk.StringVar(value='yuyv')
        self.width = tk.StringVar(value='640')
        self.height = tk.StringVar(value='480')
        self.input_path = tk.StringVar()
        self.output_path = tk.StringVar()
        self.file_mode = tk.StringVar(value='file')
        self.recursive = tk.BooleanVar(value=False)  # 递归搜索开关
        
        # 预览相关变量
        self.preview_image = None  # 存储预览图像的变量
        self.preview_window = None
        self.debug_text = tk.StringVar(value="调试输出将显示在这里...")
        
        # 创建UI组件
        self.create_widgets()
    
    def create_widgets(self):
        # 主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 格式选择
        ttk.Label(main_frame, text="YUV格式:").grid(row=0, column=0, padx=10, pady=10, sticky='w')
        formats = ['yuyv', 'yvyu', 'uyvy', 'vyuy', 'nv12', 'nv21']
        format_combo = ttk.Combobox(main_frame, textvariable=self.yuv_format, values=formats, width=10)
        format_combo.grid(row=0, column=1, padx=10, pady=10, sticky='w')
        
        # 分辨率设置
        ttk.Label(main_frame, text="宽度:").grid(row=1, column=0, padx=10, pady=10, sticky='w')
        width_entry = ttk.Entry(main_frame, textvariable=self.width, width=10)
        width_entry.grid(row=1, column=1, padx=10, pady=10, sticky='w')
        
        ttk.Label(main_frame, text="高度:").grid(row=2, column=0, padx=10, pady=10, sticky='w')
        height_entry = ttk.Entry(main_frame, textvariable=self.height, width=10)
        height_entry.grid(row=2, column=1, padx=10, pady=10, sticky='w')
        
        # 输入模式
        ttk.Label(main_frame, text="输入类型:").grid(row=3, column=0, padx=10, pady=10, sticky='w')
        ttk.Radiobutton(main_frame, text="单个文件", variable=self.file_mode, value='file').grid(row=3, column=1, sticky='w')
        ttk.Radiobutton(main_frame, text="整个文件夹", variable=self.file_mode, value='folder').grid(row=3, column=2, sticky='w')
        
        # 递归搜索选项（仅文件夹模式时启用）
        self.recursive_frame = ttk.Frame(main_frame)
        self.recursive_frame.grid(row=4, column=0, columnspan=4, sticky='w')
        ttk.Checkbutton(self.recursive_frame, text="包含子文件夹", variable=self.recursive).grid(row=0, column=0, padx=10, sticky='w')
        
        # 输入路径
        ttk.Label(main_frame, text="输入路径:").grid(row=5, column=0, padx=10, pady=10, sticky='w')
        input_entry = ttk.Entry(main_frame, textvariable=self.input_path, width=50)
        input_entry.grid(row=5, column=1, columnspan=2, padx=10, pady=10)
        ttk.Button(main_frame, text="浏览...", command=self.browse_input).grid(row=5, column=3, padx=5, pady=10)
        
        # 输出路径
        ttk.Label(main_frame, text="输出文件夹:").grid(row=6, column=0, padx=10, pady=10, sticky='w')
        output_entry = ttk.Entry(main_frame, textvariable=self.output_path, width=50)
        output_entry.grid(row=6, column=1, columnspan=2, padx=10, pady=10)
        ttk.Button(main_frame, text="浏览...", command=self.browse_output).grid(row=6, column=3, padx=5, pady=10)
        
        # 操作按钮框架
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=7, column=0, columnspan=4, pady=15)
        
        # 预览按钮（仅文件模式可用）- 修复：绑定到show_preview函数
        self.preview_btn = ttk.Button(button_frame, text="预览", command=self.show_preview, state=tk.DISABLED)
        self.preview_btn.pack(side=tk.LEFT, padx=5)
        
        # 转换按钮
        convert_btn = ttk.Button(button_frame, text="转换为BMP", command=self.convert)
        convert_btn.pack(side=tk.LEFT, padx=5)
        
        # 调试按钮
        debug_btn = ttk.Button(button_frame, text="调试输出", command=self.debug_output)
        debug_btn.pack(side=tk.LEFT, padx=5)
        
        # 状态栏
        status_frame = ttk.Frame(self.root)
        status_frame.pack(fill=tk.X, side=tk.BOTTOM)
        
        self.status = ttk.Label(status_frame, text="就绪", relief=tk.SUNKEN, anchor=tk.W)
        self.status.pack(fill=tk.X, padx=5, pady=5)
        
        # 帮助信息
        help_label = ttk.Label(main_frame, text="支持格式: yuyv, yvyu, uyvy, vyuy, nv12, nv21 | 仅支持.yuv后缀文件", foreground="gray")
        help_label.grid(row=8, column=0, columnspan=4, pady=5)
        
        # 调试信息框架
        debug_frame = ttk.LabelFrame(main_frame, text="调试输出", padding=10)
        debug_frame.grid(row=9, column=0, columnspan=4, sticky='we', padx=10, pady=10)
        
        debug_text = tk.Text(debug_frame, width=100, height=10, wrap=tk.WORD)
        debug_text.pack(fill=tk.BOTH, expand=True)
        debug_text.insert(tk.END, "调试输出将显示在这里...")
        self.debug_text_widget = debug_text
        
        # 绑定输入类型变化事件
        self.file_mode.trace_add('write', self.update_ui)
        self.input_path.trace_add('write', self.update_preview_button)
        self.update_ui()
    
    def log_debug(self, message):
        """记录调试信息并更新UI"""
        timestamp = time.strftime("%H:%M:%S", time.localtime())
        formatted_msg = f"[{timestamp}] {message}"
        
        # 记录到日志文件
        logging.debug(message)
        
        # 更新调试文本窗口
        self.debug_text_widget.insert(tk.END, formatted_msg + "\n")
        self.debug_text_widget.see(tk.END)
        
        # 更新状态栏
        self.status.config(text=message)
        self.root.update()
    
    def update_ui(self, *args):
        # 根据输入类型显示/隐藏递归选项
        if self.file_mode.get() == 'folder':
            self.recursive_frame.grid()
            self.preview_btn.config(state=tk.DISABLED)
        else:
            self.recursive_frame.grid_remove()
            self.update_preview_button()
    
    def update_preview_button(self, *args):
        # 当输入路径是文件时启用预览按钮
        if self.file_mode.get() == 'file' and self.input_path.get() and os.path.isfile(self.input_path.get()):
            self.preview_btn.config(state=tk.NORMAL)
        else:
            self.preview_btn.config(state=tk.DISABLED)
    
    def browse_input(self):
        if self.file_mode.get() == 'file':
            path = filedialog.askopenfilename(
                title="选择YUV文件",
                filetypes=[("YUV文件", "*.yuv"), ("所有文件", "*.*")]
            )
        else:
            path = filedialog.askdirectory(title="选择YUV文件夹")
        if path:
            self.input_path.set(path)
            self.update_preview_button()
    
    def browse_output(self):
        path = filedialog.askdirectory(title="选择输出文件夹")
        if path:
            self.output_path.set(path)
    
    def debug_output(self):
        """显示调试信息"""
        debug_info = f"调试信息:\n"
        debug_info += f"输入路径: {self.input_path.get()}\n"
        debug_info += f"输出路径: {self.output_path.get()}\n"
        debug_info += f"宽度: {self.width.get()}\n"
        debug_info += f"高度: {self.height.get()}\n"
        debug_info += f"格式: {self.yuv_format.get()}\n"
        debug_info += f"模式: {self.file_mode.get()}\n"
        
        if self.file_mode.get() == 'file' and self.input_path.get():
            file_path = self.input_path.get()
            if os.path.exists(file_path):
                file_size = os.path.getsize(file_path)
                debug_info += f"文件大小: {file_size} 字节\n"
                
                # 计算预期帧大小
                try:
                    width = int(self.width.get())
                    height = int(self.height.get())
                    yuv_format = self.yuv_format.get()
                    
                    if yuv_format in ['yuyv', 'yvyu', 'uyvy', 'vyuy']:
                        frame_size = width * height * 2
                        debug_info += f"预期帧大小: {frame_size} 字节\n"
                        debug_info += f"实际/预期: {file_size}/{frame_size} = {file_size/frame_size:.1f} 帧\n"
                    elif yuv_format in ['nv12', 'nv21']:
                        frame_size = width * height * 3 // 2
                        debug_info += f"预期帧大小: {frame_size} 字节\n"
                        debug_info += f"实际/预期: {file_size}/{frame_size} = {file_size/frame_size:.1f} 帧\n"
                except:
                    pass
        
        self.log_debug(debug_info)
        messagebox.showinfo("调试信息", debug_info)
    
    # 修复：将函数名从preview_image改为show_preview，避免与变量重名
    def show_preview(self):
        """预览YUV文件的第一帧 - 带详细调试过程"""
        try:
            # 清空调试输出
            self.debug_text_widget.delete(1.0, tk.END)
            self.log_debug("开始执行预览功能...")
            
            # 步骤1: 验证参数
            self.log_debug("步骤1: 验证参数...")
            width = int(self.width.get())
            height = int(self.height.get())
            yuv_format = self.yuv_format.get()
            input_path = self.input_path.get()
            
            if width <= 0 or height <= 0:
                self.log_debug("错误: 宽度和高度必须是正整数")
                messagebox.showerror("错误", "宽度和高度必须是正整数")
                return
                
            if not input_path or not os.path.isfile(input_path):
                self.log_debug("错误: 输入文件不存在")
                messagebox.showerror("错误", "输入文件不存在")
                return
            
            # 步骤2: 计算帧大小
            self.log_debug("步骤2: 计算帧大小...")
            if yuv_format in ['yuyv', 'yvyu', 'uyvy', 'vyuy']:
                frame_size = width * height * 2
            elif yuv_format in ['nv12', 'nv21']:
                frame_size = width * height * 3 // 2
            else:
                raise ValueError("不支持的YUV格式")
            
            self.log_debug(f"计算帧大小: {frame_size} 字节")
            
            # 步骤3: 读取文件
            self.log_debug("步骤3: 读取文件...")
            with open(input_path, 'rb') as f:
                file_size = os.path.getsize(input_path)
                self.log_debug(f"文件大小: {file_size} 字节")
                
                if file_size < frame_size:
                    error_msg = f"错误: 文件大小({file_size}字节)小于预期帧大小({frame_size}字节)"
                    self.log_debug(error_msg)
                    raise ValueError(error_msg)
                
                yuv_data = np.frombuffer(f.read(frame_size), dtype=np.uint8)
                self.log_debug(f"成功读取: {len(yuv_data)} 字节")
            
            # 步骤4: 提取Y分量
            self.log_debug("步骤4: 提取Y分量...")
            if yuv_format in ['yuyv', 'yvyu', 'uyvy', 'vyuy']:
                self.log_debug(f"使用打包格式提取: {yuv_format}")
                y_channel = self.extract_packed_y(yuv_data, yuv_format, width, height)
            elif yuv_format in ['nv12', 'nv21']:
                self.log_debug(f"使用平面格式提取: {yuv_format}")
                y_channel = self.extract_planar_y(yuv_data, width, height)
            else:
                raise ValueError("不支持的YUV格式")
            
            # 步骤5: 验证Y通道数据
            self.log_debug("步骤5: 验证Y通道数据...")
            self.log_debug(f"Y通道形状: {y_channel.shape}")
            self.log_debug(f"Y通道数据类型: {y_channel.dtype}")
            self.log_debug(f"Y通道值范围: {y_channel.min()} - {y_channel.max()}")
            
            # 步骤6: 创建PIL图像
            self.log_debug("步骤6: 创建PIL图像...")
            img = Image.fromarray(y_channel, 'L')
            self.log_debug(f"图像模式: {img.mode}, 图像大小: {img.size}")
            
            # 步骤7: 创建预览窗口
            self.log_debug("步骤7: 创建预览窗口...")
            if self.preview_window and self.preview_window.winfo_exists():
                self.preview_window.destroy()
            
            self.preview_window = tk.Toplevel(self.root)
            self.preview_window.title("预览 - " + os.path.basename(input_path))
            self.preview_window.geometry("800x650")
            
            # 创建框架
            preview_frame = ttk.Frame(self.preview_window, padding=10)
            preview_frame.pack(fill=tk.BOTH, expand=True)
            
            # 创建画布用于显示图像
            canvas = tk.Canvas(preview_frame, width=640, height=480, bg='#f0f0f0')
            canvas.pack(fill=tk.BOTH, expand=True, pady=10)
            
            # 步骤8: 显示图像
            self.log_debug("步骤8: 显示图像...")
            try:
                # 调整图像大小以适应预览窗口
                max_width = 600
                max_height = 400
                img_width, img_height = img.size
                
                # 计算缩放比例
                scale = min(max_width / img_width, max_height / img_height)
                new_width = int(img_width * scale)
                new_height = int(img_height * scale)
                
                # 缩放图像
                img_resized = img.resize((new_width, new_height), Image.LANCZOS)
                
                # 将PIL图像转换为Tkinter PhotoImage
                self.preview_image = ImageTk.PhotoImage(img_resized)
                
                # 在画布上显示图像
                canvas.create_image(
                    320,  # 画布中心X
                    240,  # 画布中心Y
                    anchor=tk.CENTER, 
                    image=self.preview_image
                )
                self.log_debug("图像显示成功")
            except Exception as img_error:
                self.log_debug(f"图像显示失败: {str(img_error)}")
                # 显示错误信息
                canvas.create_text(
                    320, 240, 
                    text="无法显示图像\n请查看调试信息", 
                    font=("Arial", 16), 
                    fill="red",
                    anchor=tk.CENTER
                )
            
            # 图像信息
            info_text = f"格式: {yuv_format} | 分辨率: {width}x{height} | 文件: {os.path.basename(input_path)}"
            ttk.Label(preview_frame, text=info_text, font=("Arial", 10, "bold")).pack(pady=5)
            
            # 调试信息
            debug_text = f"数组形状: {y_channel.shape} | 值范围: {y_channel.min()}-{y_channel.max()}"
            ttk.Label(preview_frame, text=debug_text, foreground="blue").pack(pady=5)
            
            # 操作按钮框架
            btn_frame = ttk.Frame(preview_frame)
            btn_frame.pack(pady=10)
            
            # 保存预览按钮
            save_btn = ttk.Button(btn_frame, text="保存预览", 
                                 command=lambda: self.save_preview_image(img, input_path))
            save_btn.pack(side=tk.LEFT, padx=5)
            
            # 打开原图按钮
            open_btn = ttk.Button(btn_frame, text="查看原图", 
                                  command=lambda: self.open_fullsize_image(img))
            open_btn.pack(side=tk.LEFT, padx=5)
            
            # 保存测试图像
            test_btn = ttk.Button(btn_frame, text="保存测试图像", 
                                  command=lambda: self.save_test_image(y_channel))
            test_btn.pack(side=tk.LEFT, padx=5)
            
            # 关闭按钮
            close_btn = ttk.Button(btn_frame, text="关闭", 
                                  command=self.preview_window.destroy)
            close_btn.pack(side=tk.LEFT, padx=5)
            
            # 步骤9: 保存测试图像
            self.log_debug("步骤9: 保存测试图像...")
            self.save_test_image(y_channel)
            
            self.log_debug("预览加载完成")
            
        except Exception as e:
            error_msg = f"预览失败: {str(e)}"
            self.log_debug(error_msg)
            messagebox.showerror("预览错误", error_msg)
            traceback.print_exc()
    
    def save_test_image(self, y_channel):
        """保存测试图像用于调试"""
        try:
            # 创建临时调试目录
            debug_dir = os.path.join(os.getcwd(), "debug_images")
            if not os.path.exists(debug_dir):
                os.makedirs(debug_dir)
            
            # 创建文件名
            timestamp = int(time.time())
            test_path = os.path.join(debug_dir, f"debug_{timestamp}.bmp")
            
            # 保存图像
            img = Image.fromarray(y_channel, 'L')
            img.save(test_path)
            
            self.log_debug(f"调试图像已保存到: {test_path}")
            
            # 尝试打开调试图像
            if os.name == 'nt':  # Windows
                os.startfile(test_path)
            elif os.name == 'posix':  # macOS, Linux
                opener = "open" if sys.platform == "darwin" else "xdg-open"
                subprocess.call([opener, test_path])
            
        except Exception as e:
            self.log_debug(f"保存调试图像失败: {str(e)}")
    
    def save_preview_image(self, img, input_path):
        """保存预览图像"""
        if not self.output_path.get():
            messagebox.showerror("错误", "请先设置输出文件夹")
            return
            
        output_dir = self.output_path.get()
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)
        
        output_path = os.path.join(output_dir, os.path.splitext(os.path.basename(input_path))[0] + '_preview.bmp')
        img.save(output_path)
        self.log_debug(f"预览图像已保存到: {output_path}")
        messagebox.showinfo("保存成功", f"预览图像已保存到:\n{output_path}")
    
    def open_fullsize_image(self, img):
        """用系统默认程序打开原尺寸图像"""
        try:
            # 创建临时文件
            with tempfile.NamedTemporaryFile(suffix='.bmp', delete=False) as tmp:
                temp_path = tmp.name
                img.save(temp_path)
                self.log_debug(f"创建临时文件: {temp_path}")
            
            # 用系统默认程序打开
            if os.name == 'nt':  # Windows
                os.startfile(temp_path)
            elif os.name == 'posix':  # macOS, Linux
                opener = "open" if sys.platform == "darwin" else "xdg-open"
                subprocess.call([opener, temp_path])
            
            # 提示用户稍后删除临时文件
            self.status.config(text=f"临时文件将在30秒后删除: {temp_path}")
            self.root.after(30000, lambda: self.delete_temp_file(temp_path))
            
        except Exception as e:
            error_msg = f"无法打开图像: {str(e)}"
            self.log_debug(error_msg)
            messagebox.showerror("打开错误", error_msg)
    
    def delete_temp_file(self, file_path):
        """删除临时文件"""
        try:
            if os.path.exists(file_path):
                os.unlink(file_path)
                self.log_debug(f"已删除临时文件: {file_path}")
        except Exception as e:
            self.log_debug(f"删除临时文件失败: {str(e)}")
    
    def convert(self):
        try:
            # 验证参数
            width = int(self.width.get())
            height = int(self.height.get())
            yuv_format = self.yuv_format.get()
            input_path = self.input_path.get()
            output_dir = self.output_path.get()
            
            if width <= 0 or height <= 0:
                messagebox.showerror("错误", "宽度和高度必须是正整数")
                return
                
            if not input_path:
                messagebox.showerror("错误", "请选择输入路径")
                return
                
            if not output_dir:
                messagebox.showerror("错误", "请选择输出文件夹")
                return
            
            if self.file_mode.get() == 'file':
                if not os.path.isfile(input_path) or not input_path.lower().endswith('.yuv'):
                    messagebox.showerror("错误", "输入文件不存在或不是YUV文件")
                    return
                files = [input_path]
            else:
                if not os.path.isdir(input_path):
                    messagebox.showerror("错误", "输入文件夹不存在")
                    return
                
                # 递归搜索YUV文件
                files = self.find_yuv_files(input_path, self.recursive.get())
                
                if not files:
                    messagebox.showerror("错误", "文件夹中没有找到YUV文件")
                    return
            
            # 创建输出文件夹
            if not os.path.exists(output_dir):
                os.makedirs(output_dir)
            
            # 禁用按钮防止重复点击
            self.preview_btn.config(state=tk.DISABLED)
            self.log_debug("开始转换...")
            
            # 在新线程中执行转换
            threading.Thread(target=self.perform_conversion, args=(files, width, height, yuv_format, output_dir)).start()
            
        except ValueError as ve:
            self.log_debug(f"参数错误: {str(ve)}")
            messagebox.showerror("错误", f"参数错误: {str(ve)}")
        except Exception as e:
            self.log_debug(f"发生未知错误: {str(e)}")
            messagebox.showerror("错误", f"发生未知错误: {str(e)}")
    
    def perform_conversion(self, files, width, height, yuv_format, output_dir):
        """执行实际的转换操作"""
        success_count = 0
        total_files = len(files)
        
        for i, file_path in enumerate(files):
            filename = os.path.basename(file_path)
            self.log_debug(f"正在处理 {filename} ({i+1}/{total_files})...")
            
            try:
                # 读取YUV数据
                with open(file_path, 'rb') as f:
                    # 根据格式计算帧大小
                    if yuv_format in ['yuyv', 'yvyu', 'uyvy', 'vyuy']:
                        frame_size = width * height * 2
                    elif yuv_format in ['nv12', 'nv21']:
                        frame_size = width * height * 3 // 2
                    else:
                        raise ValueError("不支持的YUV格式")
                    
                    file_size = os.path.getsize(file_path)
                    self.log_debug(f"文件大小: {file_size} 字节, 预期帧大小: {frame_size} 字节")
                    
                    if file_size < frame_size:
                        raise ValueError(f"文件大小({file_size}字节)小于预期帧大小({frame_size}字节)")
                    
                    yuv_data = np.frombuffer(f.read(frame_size), dtype=np.uint8)
                
                # 提取Y分量
                if yuv_format in ['yuyv', 'yvyu', 'uyvy', 'vyuy']:
                    y_channel = self.extract_packed_y(yuv_data, yuv_format, width, height)
                elif yuv_format in ['nv12', 'nv21']:
                    y_channel = self.extract_planar_y(yuv_data, width, height)
                else:
                    raise ValueError("不支持的YUV格式")
                
                # 调试输出
                self.log_debug(f"Y通道形状: {y_channel.shape}, 值范围: {y_channel.min()}-{y_channel.max()}")
                
                # 创建输出子目录（保持原始目录结构）
                if self.file_mode.get() == 'folder':
                    rel_path = os.path.relpath(os.path.dirname(file_path), input_path)
                    output_subdir = os.path.join(output_dir, rel_path)
                    if not os.path.exists(output_subdir):
                        os.makedirs(output_subdir)
                else:
                    output_subdir = output_dir
                
                # 创建并保存BMP
                img = Image.fromarray(y_channel, 'L')
                output_path = os.path.join(output_subdir, os.path.splitext(filename)[0] + '.bmp')
                img.save(output_path)
                success_count += 1
                self.log_debug(f"成功保存: {output_path}")
                
                # 保存调试图像
                debug_dir = os.path.join(os.getcwd(), "debug_images")
                if not os.path.exists(debug_dir):
                    os.makedirs(debug_dir)
                debug_path = os.path.join(debug_dir, f"conv_{filename}_{i}.bmp")
                img.save(debug_path)
                
            except Exception as e:
                error_msg = f"处理文件 {filename} 时出错: {str(e)}"
                self.log_debug(error_msg)
                self.root.after(0, lambda msg=error_msg: messagebox.showwarning("警告", msg))
        
        # 在主线程中显示完成消息
        self.log_debug(f"转换完成: 成功 {success_count}/{total_files} 个文件")
        self.root.after(0, lambda: messagebox.showinfo("完成", f"成功转换 {success_count}/{total_files} 个文件!"))
        self.root.after(0, lambda: self.preview_btn.config(state=tk.NORMAL))
    
    def find_yuv_files(self, folder, recursive=True):
        """递归查找文件夹中的所有YUV文件（只接受.yuv后缀）"""
        yuv_files = []
        if recursive:
            # 递归搜索所有子目录
            for root, dirs, files in os.walk(folder):
                for file in files:
                    if file.lower().endswith('.yuv'):
                        yuv_files.append(os.path.join(root, file))
        else:
            # 仅搜索当前目录
            for file in os.listdir(folder):
                if file.lower().endswith('.yuv'):
                    yuv_files.append(os.path.join(folder, file))
        return yuv_files
    
    def extract_packed_y(self, data, fmt, width, height):
        """提取打包格式的Y分量 - 带详细调试"""
        self.log_debug(f"开始提取打包格式Y分量: {fmt}")
        
        # 计算所需数据量
        frame_size = width * height * 2
        if len(data) < frame_size:
            raise ValueError(f"YUV数据不足: 需要 {frame_size} 字节, 实际 {len(data)} 字节")
        
        # 裁剪到实际帧大小
        data = data[:frame_size]
        self.log_debug(f"裁剪后数据长度: {len(data)} 字节")
        
        # 根据格式提取Y分量
        if fmt == 'yuyv' or fmt == 'yvyu':
            # YUYV: Y U Y V
            # 取所有偶数索引位置的值 (0, 2, 4, ...)
            y_data = data[0::2]
            self.log_debug(f"提取YUYV/YVYU格式: 每像素2字节, 取偶数索引")
        elif fmt == 'uyvy' or fmt == 'vyuy':
            # UYVY: U Y V Y
            # 取所有奇数索引位置的值 (1, 3, 5, ...)
            y_data = data[1::2]
            self.log_debug(f"提取UYVY/VYUY格式: 每像素2字节, 取奇数索引")
        else:
            raise ValueError(f"不支持的打包格式: {fmt}")
        
        self.log_debug(f"提取的Y数据长度: {len(y_data)} 字节")
        self.log_debug(f"期望数组大小: {width}x{height} = {width*height}")
        
        # 重塑为二维数组
        try:
            y_channel = y_data.reshape((height, width))
            self.log_debug(f"成功重塑数组为: {y_channel.shape}")
            return y_channel
        except Exception as e:
            self.log_debug(f"重塑数组失败: {len(y_data)} 值, 期望形状 ({height}, {width}) = {height*width}")
            raise e
    
    def extract_planar_y(self, data, width, height):
        """提取平面格式的Y分量 - 带详细调试"""
        self.log_debug(f"开始提取平面格式Y分量")
        
        # 计算Y分量大小
        y_size = width * height
        
        if len(data) < y_size:
            raise ValueError(f"YUV数据不足: 需要 {y_size} 字节, 实际 {len(data)} 字节")
        
        # 提取Y平面
        y_plane = data[:y_size]
        self.log_debug(f"提取Y平面数据长度: {len(y_plane)} 字节")
        self.log_debug(f"期望数组大小: {width}x{height} = {width*height}")
        
        try:
            # 重塑为二维数组
            return y_plane.reshape((height, width))
        except Exception as e:
            self.log_debug(f"重塑数组失败: {len(y_plane)} 值, 期望形状 ({height}, {width}) = {height*width}")
            raise e

if __name__ == "__main__":
    root = tk.Tk()
    # 设置中文显示
    try:
        # 使用元组格式设置字体
        root.option_add("*Font", ("Microsoft YaHei", 10))
    except:
        try:
            # 尝试其他可能的字体名称
            root.option_add("*Font", ("微软雅黑", 10))
        except:
            # 如果所有尝试都失败，使用默认字体
            pass
    
    app = YUVtoBMPConverter(root)
    root.mainloop()
